{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# imports\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import wfdb\n",
    "import ast\n",
    "import os\n",
    "import matplotlib.pyplot as plt\n",
    "from tensorflow.keras.models import Sequential, save_model\n",
    "from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Conv2D, MaxPooling2D, Reshape, Dropout, BatchNormalization\n",
    "from sklearn.model_selection import train_test_split"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Load raw data\n",
    "def load_raw_data(df, sampling_rate, path):\n",
    "    if sampling_rate == 100:\n",
    "        data = [wfdb.rdsamp(path+f) for f in df.filename_lr]\n",
    "    else:\n",
    "        data = [wfdb.rdsamp(path+f) for f in df.filename_hr]\n",
    "    data = np.array([signal for signal, meta in data])\n",
    "    return data\n",
    "\n",
    "path = os.getcwd()+'/'\n",
    "sampling_rate=500\n",
    "\n",
    "# load and convert annotation data\n",
    "Y = pd.read_csv('data/physionet/ptbxl_database.csv', index_col='ecg_id')\n",
    "\n",
    "# only take top x rows\n",
    "#Y = Y.head(18000)\n",
    "Y.scp_codes = Y.scp_codes.apply(lambda x: ast.literal_eval(x))\n",
    "\n",
    "# Load raw signal data\n",
    "X = load_raw_data(Y, sampling_rate, path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load scp_statements.csv for diagnostic aggregation\n",
    "agg_df = pd.read_csv('data/physionet/scp_statements.csv', index_col=0)\n",
    "agg_df = agg_df[agg_df.diagnostic == 1]\n",
    "\n",
    "def aggregate_diagnostic(y_dic):\n",
    "    tmp = []\n",
    "    for key in y_dic.keys():\n",
    "        if key in agg_df.index:\n",
    "            tmp.append(agg_df.loc[key].diagnostic_class)\n",
    "    return list(set(tmp))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Apply diagnostic superclass\n",
    "Y['diagnostic_superclass'] = Y.scp_codes.apply(aggregate_diagnostic)\n",
    "\n",
    "# create Y to target superclasses with 'CD' in them\n",
    "y = Y['diagnostic_superclass'].astype(str).str.contains('CD').astype(int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[NORM]                 9069\n",
      "[MI]                   2532\n",
      "[STTC]                 2400\n",
      "[CD]                   1708\n",
      "[MI, CD]               1297\n",
      "[STTC, HYP]             781\n",
      "[STTC, MI]              595\n",
      "[HYP]                   535\n",
      "[STTC, CD]              471\n",
      "[]                      411\n",
      "[NORM, CD]              407\n",
      "[STTC, MI, HYP]         358\n",
      "[CD, HYP]               300\n",
      "[STTC, MI, CD]          223\n",
      "[STTC, CD, HYP]         211\n",
      "[MI, HYP]               183\n",
      "[STTC, MI, CD, HYP]     155\n",
      "[MI, CD, HYP]           117\n",
      "[NORM, STTC]             28\n",
      "[NORM, STTC, CD]          5\n",
      "[MI, STTC]                4\n",
      "[MI, STTC, HYP]           3\n",
      "[NORM, CD, HYP]           2\n",
      "[NORM, HYP]               2\n",
      "[MI, STTC, CD, HYP]       1\n",
      "[NORM, MI, CD, HYP]       1\n",
      "Name: diagnostic_superclass, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(Y['diagnostic_superclass'].value_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Remove records with missing ECGs\n",
    "\n",
    "# Find all records that have at least one missing value in the 12-length arrays\n",
    "missing_values_mask = np.any(np.isnan(X), axis=(1,2)) | np.any(np.apply_along_axis(lambda x: len(x)==0, axis=2, arr=X), axis=1)\n",
    "\n",
    "# Get the indices of the records that have missing values\n",
    "missing_values_indices = np.where(missing_values_mask)[0]\n",
    "data_without_missing_values = X[~missing_values_mask]\n",
    "\n",
    "print(\"Indices of removed records:\", missing_values_indices)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#filter data using ECGMatrix\n",
    "from filters.ecgMatrix import ecgMatrix\n",
    "\n",
    "ecgm = ecgMatrix(X)\n",
    "X_filtered = ecgm.fit()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Split data into training and testing sets\n",
    "y = y.reset_index(drop=True)\n",
    "\n",
    "X_train_valid, X_test, y_train_valid, y_test = train_test_split(X_filtered, y, test_size=0.2, random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "17439\n",
      "17439\n"
     ]
    }
   ],
   "source": [
    "print(len(X_train_valid))\n",
    "print(len(y_train_valid))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Class 0 makes up 77.72% of the data\n",
      "Class 1 makes up 22.28% of the data\n"
     ]
    }
   ],
   "source": [
    "# Assuming y is a 1D numpy array of class labels\n",
    "classes, counts = np.unique(y_train_valid, return_counts=True)\n",
    "\n",
    "# Print the class distribution as percentages\n",
    "total_samples = len(y_train_valid)\n",
    "for i, c in enumerate(classes):\n",
    "    class_pct = counts[i] / total_samples * 100\n",
    "    print('Class {} makes up {:.2f}% of the data'.format(c, class_pct))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_train_valid = y_train_valid.reset_index(drop=True)\n",
    "class_0_idx = y_train_valid[y_train_valid == 0].index\n",
    "\n",
    "n_samples = 4500\n",
    "sampled_class_0_idx = np.random.choice(class_0_idx, size=n_samples, replace=False)\n",
    "\n",
    "class_1_idx = y_train_valid[y_train_valid == 1].index\n",
    "idx_sampled = np.concatenate([sampled_class_0_idx, class_1_idx])\n",
    "idx_sampled.sort()\n",
    "\n",
    "X_downsampled = X_train_valid[idx_sampled, :]\n",
    "y_downsampled = y_train_valid.iloc[idx_sampled]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Class 0 makes up 53.67% of the data\n",
      "Class 1 makes up 46.33% of the data\n"
     ]
    }
   ],
   "source": [
    "# Assuming y is a 1D numpy array of class labels\n",
    "classes, counts = np.unique(y_downsampled, return_counts=True)\n",
    "\n",
    "# Print the class distribution as percentages\n",
    "total_samples = len(y_downsampled)\n",
    "for i, c in enumerate(classes):\n",
    "    class_pct = counts[i] / total_samples * 100\n",
    "    print('Class {} makes up {:.2f}% of the data'.format(c, class_pct))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Split data into training and validation sets\n",
    "X_train, X_validation, y_train, y_validation = train_test_split(X_downsampled, y_downsampled, test_size=0.125, random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define model architecture\n",
    "model = Sequential()\n",
    "model.add(Reshape((5000,12,1), input_shape=(5000,12)))\n",
    "model.add(Conv2D(filters=32, kernel_size=(30,6), activation='relu'))\n",
    "model.add(MaxPooling2D(pool_size=(10,2)))\n",
    "model.add(Dropout(0.6))\n",
    "model.add(Conv2D(filters=64, kernel_size=(3,3), activation='relu'))\n",
    "model.add(MaxPooling2D(pool_size=(4,1)))\n",
    "model.add(Dropout(0.6))\n",
    "model.add(Conv2D(filters=16, kernel_size=(4,1), activation='relu'))\n",
    "model.add(MaxPooling2D(pool_size=(2,1)))\n",
    "model.add(Dropout(0.6))\n",
    "model.add(Flatten())\n",
    "model.add(Dense(units=128, activation='relu'))\n",
    "model.add(Dense(1, activation='sigmoid'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2023-04-23 14:38:43.705798: W tensorflow/tsl/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "29/29 [==============================] - 198s 7s/step - loss: 0.6965 - accuracy: 0.5278 - val_loss: 0.6923 - val_accuracy: 0.5148\n",
      "Epoch 2/20\n",
      "29/29 [==============================] - 193s 7s/step - loss: 0.6863 - accuracy: 0.5581 - val_loss: 0.6881 - val_accuracy: 0.5291\n",
      "Epoch 3/20\n",
      "29/29 [==============================] - 196s 7s/step - loss: 0.6757 - accuracy: 0.5675 - val_loss: 0.6792 - val_accuracy: 0.5415\n",
      "Epoch 4/20\n",
      "29/29 [==============================] - 194s 7s/step - loss: 0.6601 - accuracy: 0.6029 - val_loss: 0.6613 - val_accuracy: 0.5710\n",
      "Epoch 5/20\n",
      "29/29 [==============================] - 198s 7s/step - loss: 0.6264 - accuracy: 0.6604 - val_loss: 0.6324 - val_accuracy: 0.6940\n",
      "Epoch 6/20\n",
      "29/29 [==============================] - 192s 7s/step - loss: 0.6014 - accuracy: 0.6941 - val_loss: 0.5915 - val_accuracy: 0.7159\n",
      "Epoch 7/20\n",
      "29/29 [==============================] - 191s 7s/step - loss: 0.5693 - accuracy: 0.7140 - val_loss: 0.5773 - val_accuracy: 0.7302\n",
      "Epoch 8/20\n",
      "29/29 [==============================] - 191s 7s/step - loss: 0.5420 - accuracy: 0.7385 - val_loss: 0.5600 - val_accuracy: 0.7369\n",
      "Epoch 9/20\n",
      "29/29 [==============================] - 184s 6s/step - loss: 0.5275 - accuracy: 0.7503 - val_loss: 0.5349 - val_accuracy: 0.7512\n",
      "Epoch 10/20\n",
      "29/29 [==============================] - 183s 6s/step - loss: 0.5129 - accuracy: 0.7570 - val_loss: 0.5059 - val_accuracy: 0.7722\n",
      "Epoch 11/20\n",
      "29/29 [==============================] - 183s 6s/step - loss: 0.4884 - accuracy: 0.7739 - val_loss: 0.4928 - val_accuracy: 0.7779\n",
      "Epoch 12/20\n",
      "29/29 [==============================] - 183s 6s/step - loss: 0.4776 - accuracy: 0.7824 - val_loss: 0.4940 - val_accuracy: 0.7855\n",
      "Epoch 13/20\n",
      "29/29 [==============================] - 604s 21s/step - loss: 0.4700 - accuracy: 0.7860 - val_loss: 0.4933 - val_accuracy: 0.7798\n",
      "Epoch 14/20\n",
      "29/29 [==============================] - 187s 6s/step - loss: 0.4636 - accuracy: 0.7916 - val_loss: 0.4582 - val_accuracy: 0.8036\n",
      "Epoch 15/20\n",
      "29/29 [==============================] - 183s 6s/step - loss: 0.4490 - accuracy: 0.7981 - val_loss: 0.4449 - val_accuracy: 0.8227\n",
      "Epoch 16/20\n",
      "29/29 [==============================] - 182s 6s/step - loss: 0.4395 - accuracy: 0.8032 - val_loss: 0.4475 - val_accuracy: 0.8065\n",
      "Epoch 17/20\n",
      "29/29 [==============================] - 188s 6s/step - loss: 0.4368 - accuracy: 0.8048 - val_loss: 0.4405 - val_accuracy: 0.8151\n",
      "Epoch 18/20\n",
      "29/29 [==============================] - 182s 6s/step - loss: 0.4328 - accuracy: 0.8062 - val_loss: 0.4321 - val_accuracy: 0.8132\n",
      "Epoch 19/20\n",
      "29/29 [==============================] - 183s 6s/step - loss: 0.4353 - accuracy: 0.8077 - val_loss: 0.4467 - val_accuracy: 0.8141\n",
      "Epoch 20/20\n",
      "29/29 [==============================] - 186s 6s/step - loss: 0.4305 - accuracy: 0.8079 - val_loss: 0.4373 - val_accuracy: 0.8160\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<keras.callbacks.History at 0x145a37640>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "# Compile the model\n",
    "model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])\n",
    "\n",
    "# Train the model\n",
    "model.fit(X_train, y_train, epochs=20, batch_size=256, validation_data=(X_validation, y_validation))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "137/137 [==============================] - 21s 150ms/step - loss: 0.4281 - accuracy: 0.8206\n",
      "Test accuracy: 0.8206421732902527\n"
     ]
    }
   ],
   "source": [
    "# Evaluate the model on the test set\n",
    "test_loss, test_acc = model.evaluate(X_test, y_test)\n",
    "print('Test accuracy:', test_acc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "137/137 [==============================] - 21s 149ms/step\n"
     ]
    }
   ],
   "source": [
    "# Use the trained model to make predictions on the test set\n",
    "y_pred = model.predict(X_test)\n",
    "y_pred_classes = (y_pred > 0.55).astype(int)\n",
    "y_test_classes = y_test.values\n",
    "# Print the confusion matrix\n",
    "from sklearn.metrics import confusion_matrix\n",
    "\n",
    "cm = confusion_matrix(y_test_classes, y_pred_classes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "labels = ['Non-CD', 'CD']\n",
    "\n",
    "# Plot the confusion matrix\n",
    "fig, ax = plt.subplots()\n",
    "im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)\n",
    "ax.figure.colorbar(im, ax=ax)\n",
    "ax.set(xticks=np.arange(cm.shape[1]),\n",
    "       yticks=np.arange(cm.shape[0]),\n",
    "       xticklabels=labels, yticklabels=labels,\n",
    "       title='Confusion matrix',\n",
    "       ylabel='True label',\n",
    "       xlabel='Predicted label')\n",
    "\n",
    "# Rotate the x-axis labels\n",
    "plt.setp(ax.get_xticklabels(), rotation=45, ha=\"right\",\n",
    "         rotation_mode=\"anchor\")\n",
    "\n",
    "# Loop over the data and create a text annotation for each cell\n",
    "for i in range(cm.shape[0]):\n",
    "    for j in range(cm.shape[1]):\n",
    "        ax.text(j, i, format(cm[i, j], 'd'),\n",
    "                ha=\"center\", va=\"center\",\n",
    "                color=\"white\" if cm[i, j] > cm.max() / 2. else \"black\")\n",
    "fig.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAioAAAHFCAYAAADcytJ5AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAs60lEQVR4nO3deVxVdf7H8fdFFgUBd1FBRHNfMuWnaaW4lqa51KRtoumUmaWplUuFloXp5LRokqVoUyljo9U0ZVHjQqVNrlk6aa6QqJkJqIkI5/eHD+90AxWul3u+V17Px+M+Hp3vPefcz/lCnLff8z3nOizLsgQAAGAgP7sLAAAAuBCCCgAAMBZBBQAAGIugAgAAjEVQAQAAxiKoAAAAYxFUAACAsQgqAADAWAQVAABgLIIKUEoWLVokh8OhDRs2FPl+nz59VK9ePZe2evXqaejQoSX6nK+++kpTp07V8ePH3Sv0CjF16lQ5HI4Lvvbt22d3iR7hcDg0depUu8sAvMbf7gIA/M+KFSsUFhZWom2++uorTZs2TUOHDlWlSpVKpzAfsnLlSoWHhxdqr1Wrlg3VALhcBBXAINdcc43dJZRYXl6eHA6H/P3N+HPStm1bVatWze4yAHgIl34Ag/zx0k9BQYGmT5+uxo0bq0KFCqpUqZJatWqll156SdK5yx2PPvqoJCkmJsZ5mWP16tXO7WfOnKkmTZooKChINWrU0JAhQ5SRkeHyuZZl6bnnnlN0dLTKly+v2NhYpaamKi4uTnFxcc71Vq9eLYfDob/97W8aP3686tSpo6CgIP3444/6+eefNWrUKDVr1kwVK1ZUjRo11LVrV6Wlpbl81r59++RwODRr1iw9//zzqlevnipUqKC4uDjt3LlTeXl5mjhxomrXrq3w8HANGDBAR44c8Vgfz5gxQ35+fvrnP//p0j506FAFBwdr27ZtkqTTp09r/Pjxat26tcLDw1WlShV16NBB77//fqF9OhwOjR49WsnJyc6fVWxsrNavXy/LsjRr1izFxMSoYsWK6tq1q3788UeX7ePi4tSiRQulpaXp2muvVYUKFVSnTh09+eSTys/Pv+QxHTp0SPfff78iIyMVGBiomJgYTZs2TWfPnr2MngLMYMY/gYArWH5+fpEnjOJ8cfnMmTM1depUPfHEE+rUqZPy8vL03//+1zkfZcSIETp27JheeeUVLV++3Hl5o1mzZpKkBx54QPPnz9fo0aPVp08f7du3T08++aRWr16tTZs2OUcepkyZosTERN13330aOHCg0tPTNWLECOXl5alRo0aF6po0aZI6dOigpKQk+fn5qUaNGvr5558lSQkJCYqIiNCJEye0YsUKxcXF6fPPP3cJPJI0d+5ctWrVSnPnztXx48c1fvx49e3bV+3bt1dAQIAWLlyo/fv3a8KECRoxYoQ++OADt/vb4XCoXLlykqTHH39caWlpio+P1+bNmxUdHa3k5GQtXrxYb7zxhlq2bClJys3N1bFjxzRhwgTVqVNHZ86c0WeffaaBAwcqOTlZQ4YMcfmMDz/8UJs3b9aMGTPkcDj0+OOP6+abb1Z8fLz27NmjOXPmKCsrS+PGjdOtt96qLVu2yOFwOLc/dOiQBg8erIkTJ+rpp5/Wv/71L02fPl2//vqr5syZc8HjPXTokNq1ayc/Pz899dRTatCggdatW6fp06dr3759Sk5OLla/AcayAJSK5ORkS9JFX9HR0S7bREdHW/Hx8c7lPn36WK1bt77o58yaNcuSZO3du9elfceOHZYka9SoUS7tX3/9tSXJmjx5smVZlnXs2DErKCjIGjRokMt669atsyRZnTt3dratWrXKkmR16tTpksd/9uxZKy8vz+rWrZs1YMAAZ/vevXstSdbVV19t5efnO9tffPFFS5J1yy23uOxn7NixliQrKyvrop+XkJBwwX5u0KCBy7pHjx61IiMjrXbt2lmbNm2ygoODrbvvvrtYxzN8+HDrmmuucXlPkhUREWGdOHHC2fbee+9ZkqzWrVtbBQUFhY7z22+/dbZ17tzZkmS9//77Lvv985//bPn5+Vn79+93+ayEhATn8v33329VrFjRZR3Lsqy//OUvliTr+++/v+hxAabj0g9Qyt5880198803hV7XX3/9Jbdt166dtm7dqlGjRumTTz5RdnZ2sT931apVklToLqJ27dqpadOm+vzzzyVJ69evV25urm6//XaX9a699tpCdyWdd+uttxbZnpSUpDZt2qh8+fLy9/dXQECAPv/8c+3YsaPQur1795af3//+BDVt2lSSdPPNN7usd779wIEDFzhSV5999lmhvn7vvfdc1qlatapSUlK0adMmdezYUXXr1lVSUlKhfS1btkzXXXedKlas6DyeBQsWFHk8Xbp0UUhISKG6e/Xq5TJycr59//79LtuHhobqlltucWm78847VVBQoLVr117weD/88EN16dJFtWvX1tmzZ52vXr16SZLWrFlzwW0BX8ClH6CUNW3aVLGxsYXaw8PDlZ6eftFtJ02apJCQEL311ltKSkpSuXLl1KlTJz3//PNF7vP3fvnlF0lF3+1Su3Zt54ny/Ho1a9YstF5RbRfa5+zZszV+/HiNHDlSzzzzjKpVq6Zy5crpySefLPLEXqVKFZflwMDAi7afPn26yFr+6Oqrry7WZNr27durefPm2rp1qx544AGXkCFJy5cv1+23364//elPevTRRxURESF/f3/NmzdPCxcu9PjxFNXXERERkv73MyrK4cOH9c9//lMBAQFFvn/06NELbgv4AoIKYDB/f3+NGzdO48aN0/Hjx/XZZ59p8uTJuvHGG5Wenq7g4OALblu1alVJUmZmpiIjI13eO3jwoPNkfn69w4cPF9rHoUOHihxV+f0IwXlvvfWW4uLiNG/ePJf2nJycix+kTRISErRt2za1bdtWTz31lPr06aP69es733/rrbcUExOjlJQUl+PNzc0tlXou1P/S/35GRalWrZpatWqlZ599tsj3a9eu7ZkCAZtw6QfwEZUqVdJtt92mBx98UMeOHXM+wCwoKEiS9Ntvv7ms37VrV0nnTri/980332jHjh3q1q2bpHMjC0FBQUpJSXFZb/369YUuT1yMw+Fw1nLet99+q3Xr1hV7H96SmpqqxMREPfHEE0pNTVV4eLgGDRqkM2fOONdxOBwKDAwsNOG1qLt+PCEnJ6fQhOF33nlHfn5+6tSp0wW369Onj7777js1aNBAsbGxhV4EFfg6RlQAg/Xt21ctWrRQbGysqlevrv379+vFF19UdHS0GjZsKEnOu1ReeuklxcfHKyAgQI0bN1bjxo1133336ZVXXpGfn5969erlvOsnKipKjzzyiKRzlybGjRunxMREVa5cWQMGDFBGRoamTZumWrVqucwjuZg+ffromWeeUUJCgjp37qwffvhBTz/9tGJiYrx6m+zGjRuLfOBbs2bNFBYWpszMTN19993q3LmzEhIS5Ofnp5SUFHXq1EmPPfaYXnzxRefxLF++XKNGjdJtt92m9PR0PfPMM6pVq5Z27drl8bqrVq2qBx54QAcOHFCjRo300Ucf6fXXX9cDDzygunXrXnC7p59+Wqmp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EUEFgEf16dNHNWrU0KJFi1zaT506pZSUFPXv31933HGHIiMjFRwcrJYtW2rJkiUX3afD4dB7773n0lapUiWXz/jpp580aNAgVa5cWVWrVlW/fv20b98+zxwUANsQVAB4lL+/v4YMGaJFixbp918ltmzZMp05c0YjRoxQ27Zt9eGHH+q7777Tfffdp3vuuUdff/2125956tQpdenSRRUrVtTatWv1xRdfqGLFirrpppt05swZTxwWAJsQVAB43L333qt9+/Zp9erVzraFCxdq4MCBqlOnjiZMmKDWrVurfv36euihh3TjjTdq2bJlbn/e0qVL5efnpzfeeEMtW7ZU06ZNlZycrAMHDrjUAMD3+NtdAIArT5MmTdSxY0ctXLhQXbp00e7du5WWlqZPP/1U+fn5mjFjhlJSUvTTTz8pNzdXubm5CgkJcfvzNm7cqB9//FGhoaEu7adPn9bu3bsv93AA2IigAqBUDB8+XKNHj9bcuXOVnJys6OhodevWTbNmzdJf//pXvfjii2rZsqVCQkI0duzYi16icTgcLpeRJCkvL8/53wUFBWrbtq3efvvtQttWr17dcwcFwOsIKgBKxe23364xY8bonXfe0eLFi/XnP/9ZDodDaWlp6tevn+6++25J50LGrl271LRp0wvuq3r16srMzHQu79q1S6dOnXIut2nTRikpKapRo4bCwsJK76AAeB1zVACUiooVK2rQoEGaPHmyDh48qKFDh0qSrrrqKqWmpuqrr77Sjh07dP/99+vQoUMX3VfXrl01Z84cbdq0SRs2bNDIkSMVEBDgfP+uu+5StWrV1K9fP6WlpWnv3r1as2aNxowZo4yMjNI8TACljKACoNQMHz5cv/76q7p37666detKkp588km1adNGN954o+Li4hQREaH+/ftfdD8vvPCCoqKi1KlTJ915552aMGGCgoODne8HBwdr7dq1qlu3rgYOHKimTZvq3nvv1W+//cYIC+DjHNYfL/wCAAAYghEVAABgLIIKAAAwFkEFAAAYi6ACAACMRVABAADGIqgAAABjEVQAAICxCCoAAMBYBBUAAGAsggoAADAWQQUAABiLoAIAAIz1/yaCD0LX3Em/AAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot the histogram\n",
    "plt.hist(y_pred, bins=50, density=True, alpha=0.75)\n",
    "\n",
    "# Add axis labels and title\n",
    "plt.xlabel('Value')\n",
    "plt.ylabel('Frequency')\n",
    "plt.title('Histogram Example')\n",
    "\n",
    "# Display the plot\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AUC: 0.8734291084736194\n"
     ]
    }
   ],
   "source": [
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "# y_pred is an array of predicted probabilities (e.g., output of predict_proba())\n",
    "# y_test is an array of true labels (0 or 1)\n",
    "auc = roc_auc_score(y_test, y_pred)\n",
    "\n",
    "print('AUC:', auc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:absl:Found untraced functions such as _jit_compiled_convolution_op, _jit_compiled_convolution_op, _jit_compiled_convolution_op, _update_step_xla while saving (showing 4 of 4). These functions will not be directly callable after loading.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: CD_CNN_filtered/assets\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: CD_CNN_filtered/assets\n"
     ]
    }
   ],
   "source": [
    "#save model\n",
    "save_model(model, 'CD_CNN_filtered')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from keras.utils import plot_model\n",
    "\n",
    "plot_model(model, to_file='model.png', show_shapes=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ecgdetectors import Detectors\n",
    "\n",
    "test = X_downsampled\n",
    "\n",
    "detectors = Detectors(500)\n",
    "\n",
    "results = []\n",
    "for i in range(test.shape[0]):\n",
    "    row_results = []\n",
    "    for j in range(test.shape[2]):\n",
    "        input = test[i,:,j]\n",
    "        hrv = np.diff(detectors.pan_tompkins_detector(input))\n",
    "        #average interval\n",
    "        avg_interval = np.mean(hrv)\n",
    "        #standard devation interval\n",
    "        std_interval = np.std(hrv)\n",
    "        #find percentage difference that are greater than 50ms\n",
    "        hrv_diff = np.diff(hrv)\n",
    "        over50ms = 0\n",
    "        for diff in range(len(hrv_diff)):\n",
    "            if abs(hrv_diff[diff])>25:\n",
    "                over50ms += 1\n",
    "        if len(hrv_diff)>0:\n",
    "            percent_over_50 = over50ms/len(hrv_diff)\n",
    "        else:\n",
    "            percent_over_50 = 999\n",
    "\n",
    "        \n",
    "        row_results.append(avg_interval)\n",
    "        row_results.append(std_interval)\n",
    "        row_results.append(percent_over_50)\n",
    "    results.append(row_results)\n",
    "\n",
    "columns = []\n",
    "for j in range(test.shape[2]):\n",
    "    columns.append('waveform_{}_avg_int'.format(j+1))\n",
    "    columns.append('waveform_{}_std_int'.format(j+1))\n",
    "    columns.append('waveform_{}_diff_over_50ms'.format(j+1))\n",
    "df = pd.DataFrame(results, columns=columns)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_downsampled = y_downsampled.reset_index(drop=True)\n",
    "print(y_downsampled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['target'] = y_downsampled\n",
    "print(df.shape)\n",
    "df.head(30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pycaret.classification import *\n",
    "mi_clf = setup(data = df, target='target')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "compare_models()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "project",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.16"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "80d9a1379de2d41fa2b295741608ca4702c97a773ae3bbb91264b0defe5b51d0"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
